package com.dbingtech.iot.acl;

import com.google.common.util.concurrent.RateLimiter;

import java.util.concurrent.TimeUnit;

/**
 * 基于令牌桶的访问速度限制，直接用的guava库中的api进行实现
 *
 * 直接看下面的例子：RateLimiter.create(5, 20, TimeUnit.SECONDS);
 *
 * 预热时间20秒，我们从输出一看就懂了，5个每秒，意味着每次获取令牌理论上应该耗时0.2秒，这样的话5个，一共是1秒钟，对吧，但是呢，因为有预热20秒，所以一开始获取令牌的时间肯定是超过0.2秒的，我们看
 * 真实的测试输出，从0.5秒开始递减，证明一开始的预热阶段的流控是OK的，确实是在正常工作的，等到时间过去了20秒，我们往下翻，就可以看到测试输出，稳定在0.2秒，证明完全OK！
 *
 * google的研发人员还是很厉害的！
 *
 * 拿一个令牌，耗时0.0秒
 * 拿一个令牌，耗时0.59179秒
 * 拿一个令牌，耗时0.584115秒
 * 拿一个令牌，耗时0.574001秒
 * 拿一个令牌，耗时0.57183秒
 * ***********************************************************
 * 拿一个令牌，耗时0.563609秒
 * 拿一个令牌，耗时0.552603秒
 * 拿一个令牌，耗时0.537762秒
 * 拿一个令牌，耗时0.539397秒
 * 拿一个令牌，耗时0.532298秒
 * ***********************************************************
 * 拿一个令牌，耗时0.523389秒
 * 拿一个令牌，耗时0.515877秒
 * 拿一个令牌，耗时0.507634秒
 * 拿一个令牌，耗时0.499229秒
 * 拿一个令牌，耗时0.491879秒
 * ***********************************************************
 * 拿一个令牌，耗时0.48324秒
 * 拿一个令牌，耗时0.475742秒
 * 拿一个令牌，耗时0.467039秒
 * 拿一个令牌，耗时0.459824秒
 * 拿一个令牌，耗时0.450883秒
 * ***********************************************************
 * 拿一个令牌，耗时0.443333秒
 * 拿一个令牌，耗时0.436404秒
 * 拿一个令牌，耗时0.427637秒
 * 拿一个令牌，耗时0.419089秒
 * 拿一个令牌，耗时0.412018秒
 * ***********************************************************
 * 拿一个令牌，耗时0.403707秒
 * 拿一个令牌，耗时0.39553秒
 * 拿一个令牌，耗时0.386925秒
 * 拿一个令牌，耗时0.379606秒
 * 拿一个令牌，耗时0.370447秒
 * ***********************************************************
 * 拿一个令牌，耗时0.364293秒
 * 拿一个令牌，耗时0.355707秒
 * 拿一个令牌，耗时0.346816秒
 * 拿一个令牌，耗时0.339562秒
 * 拿一个令牌，耗时0.331142秒
 *
 *
 * 经过了20秒以后。。。。。。。。。。。。。。。。。。。。。。，下面每次获取令牌一直稳定在0.2秒，起到了流控的效果！
 *
 *
 * ***********************************************************
 * 拿一个令牌，耗时0.203016秒
 * 拿一个令牌，耗时0.199748秒
 * 拿一个令牌，耗时0.198884秒
 * 拿一个令牌，耗时0.199006秒
 * 拿一个令牌，耗时0.19979秒
 * ***********************************************************
 * 拿一个令牌，耗时0.198962秒
 * 拿一个令牌，耗时0.199076秒
 * 拿一个令牌，耗时0.200043秒
 * 拿一个令牌，耗时0.200023秒
 * 拿一个令牌，耗时0.199558秒
 * ***********************************************************
 * 拿一个令牌，耗时0.198493秒
 * 拿一个令牌，耗时0.200662秒
 * 拿一个令牌，耗时0.199119秒
 * 拿一个令牌，耗时0.19919秒
 * 拿一个令牌，耗时0.200037秒
 * ***********************************************************
 * 拿一个令牌，耗时0.199774秒
 * 拿一个令牌，耗时0.19966秒
 * 拿一个令牌，耗时0.198744秒
 * 拿一个令牌，耗时0.199694秒
 * 拿一个令牌，耗时0.199229秒
 * ***********************************************************
 * 拿一个令牌，耗时0.199911秒
 * 拿一个令牌，耗时0.199872秒
 * 拿一个令牌，耗时0.199406秒
 * 拿一个令牌，耗时0.200312秒
 * 拿一个令牌，耗时0.200194秒
 * ***********************************************************
 * 拿一个令牌，耗时0.200026秒
 * 拿一个令牌，耗时0.199085秒
 * 拿一个令牌，耗时0.199873秒
 * 拿一个令牌，耗时0.199626秒
 * 拿一个令牌，耗时0.199068秒
 * ***********************************************************
 * 拿一个令牌，耗时0.199663秒
 * 拿一个令牌，耗时0.198762秒
 * 拿一个令牌，耗时0.198668秒
 * 拿一个令牌，耗时0.199633秒
 * 拿一个令牌，耗时0.199462秒
 * ***********************************************************
 * 拿一个令牌，耗时0.199609秒
 * 拿一个令牌，耗时0.199219秒
 * 拿一个令牌，耗时0.20003秒
 * 拿一个令牌，耗时0.199685秒
 * 拿一个令牌，耗时0.19876秒
 * ***********************************************************
 */
public class SpeedLimiter {

    /**
     * 令牌桶限流算法，实现的效果是这样的：
     * 1、每秒钟最多对外发放1000个令牌
     * 2、每次执行之前都要调用acquire方法获取令牌，如果没有令牌了，当前线程就只能等待，不会往下执行真正的业务逻辑，这样也就保护了业务逻辑不受大流量的冲击（假设业务逻辑最多只能承受1000个客户端并发调用）
     * 3、第2个参数20,第三个参数秒，意思是在一开始的20秒内属于预热阶段，意思是我们的系统峰值是可以承受1000个客户端并发访问的，但是呢在系统刚启动的时候，悠着点，刚启动的时候因为还有一些其它的业务
     * 逻辑在执行，所以还不能达到峰值1000，所以在开始的20秒内，发放令牌的话悠着点，最终的效果就是可以控制住，在刚启动系统的20秒内，令牌的发放并不会是每秒1000个，会少于1000，这样的话，就可以
     * 防止在系统刚启动的前20秒内受到较大流量的冲击，等到过了20秒了，那么就可以按最大1000的并发客户端，尽管放马过来了
     */
    RateLimiter limiter = RateLimiter.create(1000, 20, TimeUnit.SECONDS);

    Object before() {
        limiter.acquire(1);
        return execute();
    }

    Object execute() {
        return null;
    }

    public static void main(String[] args) {

        SpeedLimiter limiter = new SpeedLimiter();

        while (true) {
            System.out.println("拿一个令牌，耗时" + limiter.limiter.acquire(1) + "秒");
            System.out.println("拿一个令牌，耗时" + limiter.limiter.acquire(1) + "秒");
            System.out.println("拿一个令牌，耗时" + limiter.limiter.acquire(1) + "秒");
            System.out.println("拿一个令牌，耗时" + limiter.limiter.acquire(1) + "秒");
            System.out.println("拿一个令牌，耗时" + limiter.limiter.acquire(1) + "秒");

            System.out.println("***********************************************************");
        }

    }
}
